package simpleHttpClient;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;

/**
 * This class takes the input stream and finds relevant data for this lab and
 * stores them in variables
 * 
 * If the encoding type is 'Chunked', then the program will read in chunks
 * 
 * @author Scott Wendlandt
 * Rebecca Chandler
 * Li Weihan
 */
public class Response {
	// The header of the Response
	private String header = "";
	// In the format of "'type'/'something'
	private String contentType = "";
	// Currently for Chunked or Not-Chunked
	private String encodingType = "";
	// The body of the response in Strings which are bytes
	private String entityBody = "";
	// The Content-Disposition field of the sample form:
	//Content-Disposition: attachment; filename="fname.ext"
	private String contentDisposition = "";
	//the Content-Length field
	private int contentLength = 0;
	//keeps track of whether the entity body has been read
	private boolean bodyHasBeenRead = false;
	//file name
	private String fileName = "";

	//input stream to read the response from
	private InputStream inputStream;
	// The Carriage Return Line Feed
	private final String CRLF = "\r\n";

	/**
	 * Constructor for the Response class.
	 * Gathers information on the header and entity body. 
	 * Decodes entity body if chunked
	 * @param input The inputStream gained through the socket connected to the server
	 */
	public Response(InputStream input) {
		inputStream = input;

		// To determine when the header is over	
		boolean headerEnd = false;
		// The current line being worked on
		String line = "";

		// headerEnd = true; indicates the end of the header
		while (!headerEnd) {
			try {
				byte[] thisByte = new byte[1];
				inputStream.read(thisByte);
				line += new String(thisByte);
				if (line.equalsIgnoreCase(CRLF)) {
					// A CRLF by itself indicates the end of the header
					headerEnd = true;
					header += CRLF;
				}
				if (line.contains(CRLF)) {
					// Assemble the header line by line
					header += line;
					if (line.contains("Content-Type:")) {
						String[] subString = line.split(";");
						contentType = subString[0].substring(line.indexOf(':') + 2);
					}
					if (line.contains("Transfer-Encoding:")) {
						encodingType = line.substring(line.indexOf(':') + 2);
					}
					if (line.contains("Content-Disposition:")) {
						contentDisposition = line.substring(line.indexOf(':') + 2);
					}
					if (line.contains("Content-Length:")) {
						contentLength = Integer.parseInt(line.substring(line.indexOf(':') + 2).trim());
					}

					line = "";
				}
			} catch (IOException e) {
				System.out.println("Error reading in the header");
			}
		}
	}
	
	/**
	 * Determines whether the the entity body is chunked or not
	 * @param fo The passed in fileoutputStream 
	 */
	public void readEntityBody(FileOutputStream fo){
		//read in the entity body if it has not yet been read and the parameter fo is not null
		if((!bodyHasBeenRead) && (fo != null)){
			if(encodingType.trim().equalsIgnoreCase("chunked")){
				readChunked(fo);
			}else {
				readNotChunked(fo);
			}
			bodyHasBeenRead = true;
		}
		try {
			fo.flush();
			fo.close();
		} catch (IOException e) {
			System.out.println("Error closing FileOutputStream.");
		}
	}

	/**
	 * Read in the chunked entity body
	 * @param fileOutputStream The output stream that will write to the file.
	 */
	private void readChunked(FileOutputStream fileOutputStream){
		// To continue the loop if the stream is still going
		boolean cont = true;
		// The hex number at the beginning of a chunk
		String hexNum = "";
		// The wanted bytes to read
		int numOfBytesToRead;
		// The actual bytes read
		int actualBytes = 0;
		//array of bytes read
		byte[] bytes;
		do {
			try {
				// Read in the hex number representing the number of bytes
				// to read in after
				byte[] thisByte = new byte[1];
				inputStream.read(thisByte);
				hexNum += new String(thisByte);
				if (hexNum.contains(CRLF)) {
					// Remove the CRLF
					hexNum = hexNum.substring(0, hexNum.length() - 2);
					if (!hexNum.equals("0")) {
						numOfBytesToRead = Integer.decode("#" + hexNum);
						hexNum = "";
						//System.out.println("Num bytes to read: " + numOfBytesToRead);
						// Determining if the wanted number of bytes
						// were actually read.
						// If not, read again with the number of
						// remaining bytes until all the bytes were
						// read.
						while (actualBytes < numOfBytesToRead) {
							bytes = new byte[numOfBytesToRead
							                 - actualBytes];
							actualBytes += inputStream.read(bytes);
							fileOutputStream.write(bytes);
						}
						actualBytes = 0;
						// To get rid of the CRLF at the end of the
						// chunk
						inputStream.read();
						inputStream.read();
						actualBytes = 0;
					} else {
						// If the line is = 0. Signifying the end of stream
						//input.close();
						cont = false;
					}
				}
			} catch (IOException e) {
				System.out.println("Reading bytes failed.");
			}
		} while (cont);
	}

	/**
	 * Read in a non-chunked entity body
	 * @param fileOutputStream The output stream that will write to the file.
	 */
	private void readNotChunked(FileOutputStream fileOutputStream){
		byte[] bytes;
		int actualBytes = 0;

		while (actualBytes < contentLength) {
			bytes = new byte[contentLength- actualBytes];
			try {
				actualBytes += inputStream.read(bytes);
			} catch (IOException e) {
				System.out.println("Error reading from Input Stream.");
			}
			try {
				fileOutputStream.write(bytes);
			} catch (IOException e) {
				System.out.println("Error writing to Output Stream.");
			}
		}
	}

	/**
	 * Returns the entityBody
	 * @return returns the full entityBody received from the web server
	 */
	public String getEntityBody() {
		return entityBody;
	}

	/**
	 * Returns the contentType
	 * @return returns the type of the content in the format of "'type'/'something'"
	 */
	public String getContentType() {
		return contentType;
	}

	/**
	 * From the Content Type in the header, determines what file extension this should be saved as
	 * @return returns the file type it should be saved as
	 */	
	public String getFileExt() {
		return "." + contentType.substring(contentType.indexOf('/')+1);
	}

	/**
	 * Returns the string which is the header of the response
	 * @return returns the header
	 */
	public String toString() {
		return header;
	}

	public String getContentDisposition() {
		return contentDisposition;
	}

	/**
	 * Assembles the name of the file to save it to and the extension
	 * @return The filename
	 */
	public String getFileName() {
		if(contentDisposition.contains("filename:")){
			fileName = contentDisposition.substring(contentDisposition.indexOf('"'),contentDisposition.length()-1);
		} else if (fileName.equals("")){
			byte[] bytes = new byte[7];
			new Random().nextInt(999);
			fileName = "" + (new Random().nextInt(999)) + getFileExt();
		}
		return fileName;
	}
}
